/**
 * 
 */
package org.dragonfire.file;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.zip.ZipFile;

import org.dragonfire.dasm.CompileException;

/**
 * @author Darth Android
 */
// TODO rewrite with methods instead of direct access
public class BufferedFile {

	public static final String LOCAL_ROOT_DIR = "srcFiles/";
	public static final String ARCHIVE_SEP_CHAR = "|";
	public int lineNum = -1;
	public final String name;
	public final String fullName;
	public final String dir;
	public final String[] data;
	private boolean isLocal = false;

	/**
	 * @return the isLocal
	 */
	public boolean isLocal() {
		return isLocal;
	}

	/**
	 * @param isLocal
	 *            the isLocal to set
	 */
	public void setLocal(boolean isLocal) {
		this.isLocal = isLocal;
	}

	public BufferedFile(String name, String dir, String[] data) {
		this.name = name;
		this.dir = dir;
		this.data = data;
		this.fullName = name;
	}

	/**
	 * Creates a new <code>WorkingFile</code>.
	 * 
	 * @param name
	 *            The name of the file.
	 * @param data
	 *            The contents of the file, one line per array element.
	 */
	public BufferedFile(String name, String[] data) {
		File file = new File(name);
		fullName = name;
		this.name = file.getName();
		this.dir = file.getPath();
		this.data = data;
	}

	public BufferedFile(String name, Scanner input) {
		ArrayList<String> data = new ArrayList<String>();
		while (input.hasNextLine())
			data.add(input.nextLine());
		this.data = data.toArray(new String[0]);
		File file = new File(name);
		this.name = file.getName();
		fullName = name;
		this.dir = file.getPath();
	}

	public BufferedFile(String name, InputStream data) {
		this(name, new Scanner(data));
	}

	public BufferedFile(String name) {
		Scanner input;
		input = new Scanner(openFile(name));
		ArrayList<String> data = new ArrayList<String>();
		while (input.hasNextLine())
			data.add(input.nextLine());
		this.data = data.toArray(new String[0]);
		boolean local = false;
		if (name.matches("<.*>")) {
			local = true;
			name = name.substring(1, name.length() - 1);
		}
		File file = new File(name);
		this.name = (local ? "<" : "") + file.getName() + (local ? ">" : "");
		fullName = file.getName();
		this.dir = file.getPath();
	}

	/**
	 * Checks if there are any available lines.
	 * 
	 * @return True if there is at least 1 more line in the file<br />
	 *         False if there isn't
	 */
	public boolean hasNextLine() {
		return lineNum < data.length - 1;
	}

	/**
	 * Returns the next line in the file.
	 * 
	 * @return The next line in the file.
	 * @throws EOFException
	 *             If there are no more lines to retrieve
	 */
	public String nextLine() {
		if (!hasNextLine())
			return null;
		return data[++lineNum];
	}

	/**
	 * Retrieves the current line from the file.
	 * 
	 * @return The current line of the file
	 */
	public String current() {
		return data[lineNum];
	}

	/**
	 * String representation of a WorkingFile in the format
	 * <code>&lt;name&gt;(&lt;currentLine&gt;/&lt;totalLines&gt;)</code>
	 */
	public String toString() {
		return name + "(" + lineNum + "/" + data.length + ")";
	}

	public static BufferedFile openBufferedFile(String filename) {
		BufferedFile file = new BufferedFile(filename);
		if (isLocalFile(file.getName()))
			file.setLocal(true);
		return file;
	}

	public static InputStream openFile(String filename) {
		if (filename == null || filename.equals(""))
			throw new CompileException("ERROR_FILE_NOT_FOUND", filename);
		if (isLocalFile(filename)) {
			return openLocal(filename.substring(1, filename.length() - 1));
		}
		try {
			if (filename.contains(ARCHIVE_SEP_CHAR)) {
				String[] parts = filename.split(Pattern.quote(ARCHIVE_SEP_CHAR), 2);
				ZipFile zf = openZipFile(parts[0]);
				return zf.getInputStream(zf.getEntry(parts[1]));
			}
			return new FileInputStream(filename);
		} catch (Exception e) {
			throw new CompileException("ERROR_FILE_NOT_FOUND", filename);
		}
	}

	private static boolean isLocalFile(String filename) {
		return filename.charAt(0) == '<' && filename.length() > 1 && filename.charAt(filename.length() - 1) == '>';
	}

	public static InputStream openLocal(String filename) {
		try {
			if (filename.equals("STDIN"))
				return System.in;
			InputStream file = BufferedFile.class.getResourceAsStream(LOCAL_ROOT_DIR + filename);
			if (file == null)
				throw new NullPointerException();
			return file;
		} catch (Exception e) {
			throw new CompileException("ERROR_FILE_NOT_FOUND", "<" + filename + ">");
		}
	}

	// TODO openRarFile()
	public static ZipFile openZipFile(String path) {
		try {
			ZipFile jf = new ZipFile(path);
			return jf;
		} catch (Exception e) {
			throw new CompileException("ERROR_FILE_NOT_FOUND", path);
		}
	}

	public static PrintStream openOutputFile(String fileName) {
		try {
			if (fileName.equalsIgnoreCase("<STDOUT>"))
				return System.out;
			if (fileName.equalsIgnoreCase("<STDERR>"))
				return System.err;
			File f = new File(fileName);
			return new PrintStream(f);
		} catch (Exception e) {
		}
		throw new CompileException("ERROR_DEST_UNWRITABLE", fileName);
	}

	/**
	 * @return the name
	 */
	public String getName() {
		return name;
	}

	public String getFullName() {
		if (isLocal())
			return getName();
		return getDirectory() + getName();
	}

	public String getDirectory() {
		return dir;
	}
}
